#include "UtilityPCH.h"
#include "UT_FilmImage.h"
#include "UT_MathHelper.h"
#include "UT_SpectrumRGB.h"
#include "UT_SpectrumSampled.h"
#include "UT_SpectrumHelper.h"
#include "UT_Log.h"

using namespace Utility;

static const int32_t  iFilterTableSize = 16;
//------------------------------------------------------------------------
UT_FilmImage::UT_FilmImage(int32_t iXRes, int32_t iYRes, UT_FilterBase* pkFilter, const float afCrop[4],const StringType& rkFilename)
	: UT_FilmBase(iXRes,iYRes)
{
	m_pkFilter = pkFilter;
	MEMCPY(m_afCropWindow,afCrop,sizeof(float) * 4);
	m_kFilename = rkFilename;
	m_iXPixelStart = UT_MathHelper::Ceil2Int(m_iXResolution * m_afCropWindow[0]);
	m_iXPixelCount = UT_MathHelper::Max(1,UT_MathHelper::Ceil2Int(m_iXResolution * m_afCropWindow[1] - m_iXPixelStart));
	m_iYPixelStart = UT_MathHelper::Ceil2Int(m_iYResolution * m_afCropWindow[2]);
	m_iYPixelCount = UT_MathHelper::Max(1,UT_MathHelper::Ceil2Int(m_iYResolution * m_afCropWindow[3] - m_iYPixelStart));
	
	m_pkPixels	   = MM_NEW UT_TVector<Pixel>(m_iXPixelCount * m_iYPixelCount);


	m_pfFilterTable = MM_NEW float[iFilterTableSize * iFilterTableSize];
	float*	ftp		= m_pfFilterTable;
	for (int y = 0; y < iFilterTableSize; ++y)
	{
		float fy = ((float)y + .5f) * m_pkFilter->m_fYWidth / iFilterTableSize;
		for (int x = 0; x < iFilterTableSize; ++x)
		{
			float fx = ((float)x + .5f) * m_pkFilter->m_fXWidth / iFilterTableSize;
			*ftp++ = m_pkFilter->Evaluate(fx, fy);
		}
	}
}
//------------------------------------------------------------------------
UT_FilmImage::~UT_FilmImage()
{
	MM_DELETE	m_pkPixels;
	MM_DELETE	m_pkFilter;
	MM_DELETE[] m_pfFilterTable;
}
//------------------------------------------------------------------------
void UT_FilmImage::AddSample(const UT_CameraSample& rkSample, const UT_SpectrumUsed& rkL)
{
	float fDiscreteImageX = rkSample.m_fImageX - 0.5f;
	float fDiscreteImageY = rkSample.m_fImageY - 0.5f;
	int x0 = UT_MathHelper::Ceil2Int(fDiscreteImageX - m_pkFilter->m_fXWidth);
	int x1 = UT_MathHelper::Floor2Int(fDiscreteImageX + m_pkFilter->m_fXWidth);
	int y0 = UT_MathHelper::Ceil2Int(fDiscreteImageY - m_pkFilter->m_fYWidth);
	int y1 = UT_MathHelper::Floor2Int(fDiscreteImageY + m_pkFilter->m_fYWidth);
	x0	   = UT_MathHelper::Max(x0, m_iXPixelStart);
	x1     = UT_MathHelper::Min(x1, m_iXPixelStart + m_iXPixelCount - 1);
	y0	   = UT_MathHelper::Max(y0, m_iYPixelStart);
	y1	   = UT_MathHelper::Min(y1, m_iYPixelStart + m_iYPixelCount - 1);
	
	if ((x1-x0) < 0 || (y1-y0) < 0)
	{
		return;
	}

	// Loop over filter support and add sample to pixel arrays
	float xyz[3];
	rkL.ToXYZ(xyz);

	// Precompute $x$ and $y$ filter table offsets
	int32_t* pifx = (int32_t*)(MM_STAND_ALLOC(sizeof(int32_t)*(x1 - x0 + 1)));
	for (int x = x0; x <= x1; ++x)
	{
		float fx	= UT_MathHelper::Abs((x - fDiscreteImageX) * m_pkFilter->m_fInvXWidth * iFilterTableSize);
		pifx[x-x0]	=UT_MathHelper::Min(UT_MathHelper::Floor2Int(fx), iFilterTableSize-1);
	}
	
	int32_t* pify = (int32_t*)(MM_STAND_ALLOC(sizeof(int32_t)*(y1 - y0 + 1)));
	for (int y = y0; y <= y1; ++y)
	{
		float fy	= UT_MathHelper::Abs((y - fDiscreteImageY) * m_pkFilter->m_fInvYWidth * iFilterTableSize);
		pify[y-y0] = UT_MathHelper::Min(UT_MathHelper::Floor2Int(fy), iFilterTableSize-1);
	}
	
	for (int y = y0; y <= y1; ++y)
	{
		for (int x = x0; x <= x1; ++x)
		{
			// Evaluate filter value at $(x,y)$ pixel
			int offset = pify[y-y0]*iFilterTableSize + pifx[x-x0];
			float filterWt = m_pfFilterTable[offset];

			// Update pixel values with filtered sample contribution
			int32_t iPixelIndex = (y - m_iYPixelStart) * m_iXPixelCount + ( x - m_iXPixelStart);
			Pixel&	rkPixel = (*m_pkPixels).GetAt(iPixelIndex);
			
			rkPixel.Lxyz[0] += filterWt * xyz[0];
			rkPixel.Lxyz[1] += filterWt * xyz[1];
			rkPixel.Lxyz[2] += filterWt * xyz[2];
			rkPixel.weightSum += filterWt;
		}
	}
}
//------------------------------------------------------------------------
void UT_FilmImage::Splat(const UT_CameraSample& rkSample, const UT_SpectrumUsed& rkL)
{
	if (rkL.HasNaNs())
	{
		GfxWriteLog(LOG_S_DEFAULT_WARNING,"ImageFilm ignoring splatted spectrum with NaN values");
		return;
	}
	
	float xyz[3];
	rkL.ToXYZ(xyz);
	int32_t x = UT_MathHelper::Floor2Int(rkSample.m_fImageX);
	int32_t y = UT_MathHelper::Floor2Int(rkSample.m_fImageY);

	if (x < m_iXPixelStart || x - m_iXPixelStart >= m_iXPixelCount ||
		y < m_iYPixelStart || y - m_iYPixelStart >= m_iYPixelCount)
		return;
	
	int32_t iPixelIndex = (y - m_iYPixelStart) * m_iXPixelCount + ( x - m_iXPixelStart);
	Pixel&	rkPixel = (*m_pkPixels).GetAt(iPixelIndex);

	rkPixel.splatXYZ[0] += xyz[0];
	rkPixel.splatXYZ[1] += xyz[1];
	rkPixel.splatXYZ[2] += xyz[2];
}
//------------------------------------------------------------------------
void UT_FilmImage::GetSampleExtent(int32_t* piXStart, int32_t* piXEnd, int* piYStart, int32_t* piYEnd) const
{
	*piXStart = UT_MathHelper::Floor2Int(m_iXPixelStart + 0.5f - m_pkFilter->m_fXWidth);
	*piXEnd   = UT_MathHelper::Floor2Int(m_iXPixelStart + 0.5f + m_iXPixelCount + m_pkFilter->m_fXWidth);

	*piYStart = UT_MathHelper::Floor2Int(m_iYPixelStart + 0.5f - m_pkFilter->m_fYWidth);
	*piYEnd   = UT_MathHelper::Floor2Int(m_iYPixelStart + 0.5f + m_iYPixelCount + m_pkFilter->m_fYWidth);
}
//------------------------------------------------------------------------
void UT_FilmImage::GetPixelExtent(int32_t* piXStart, int32_t* piXEnd, int* piYStart, int32_t* piYEnd) const
{
	*piXStart = m_iXPixelStart;
	*piXEnd	  = m_iXPixelStart + m_iXPixelCount;
	*piYStart = m_iYPixelStart;
	*piYEnd   = m_iYPixelStart + m_iYPixelCount;
}
//------------------------------------------------------------------------
void UT_FilmImage::WriteImage(float splatScale)
{
	// Convert image to RGB and compute final pixel values
	int32_t nPix		= m_iXPixelCount * m_iYPixelCount;
	float*	rgb			= MM_NEW float[3*nPix];
	int32_t offset		= 0;
	int32_t nPixelIndex	= 0;
	for (int y = 0; y < m_iYPixelCount; ++y)
	{
		for (int x = 0; x < m_iXPixelCount; ++x)
		{
			// Convert pixel XYZ color to RGB
			nPixelIndex = y * m_iXPixelCount + x;
			UT_SpectrumHelper::XYZToRGB((*m_pkPixels).GetAt(nPixelIndex).Lxyz, &rgb[3*offset]);

			// Normalize pixel with weight sum
			float weightSum = (*m_pkPixels).GetAt(nPixelIndex).weightSum;
			if (weightSum != 0.f)
			{
				float invWt = 1.f / weightSum;
				rgb[3*offset  ] = UT_MathHelper::Max(0.f, rgb[3*offset  ] * invWt);
				rgb[3*offset+1] = UT_MathHelper::Max(0.f, rgb[3*offset+1] * invWt);
				rgb[3*offset+2] = UT_MathHelper::Max(0.f, rgb[3*offset+2] * invWt);
			}

			// Add splat value at pixel
			float splatRGB[3];
			UT_SpectrumHelper::XYZToRGB((*m_pkPixels).GetAt(nPixelIndex).splatXYZ, splatRGB);
			rgb[3*offset  ] += splatScale * splatRGB[0];
			rgb[3*offset+1] += splatScale * splatRGB[1];
			rgb[3*offset+2] += splatScale * splatRGB[2];
			++offset;
		}
	}

	// Write RGB image
	//::WriteImage(filename, rgb, NULL, xPixelCount, yPixelCount,
	//	xResolution, yResolution, xPixelStart, yPixelStart);

	// Release temporary image memory
	MM_DELETE[] rgb;
}
//------------------------------------------------------------------------
void UT_FilmImage::UpdateDisplay(int x0, int y0, int x1, int y1,float splatScale)
{

}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
